Android上的OpenGL ES 使用错误 解决

Android上的OpenGL ES
学到着色这一块,除了个问题,一直没有解决:还请大神帮帮忙:
代码分为四个部分:主界面MainActivity.java、渲染类Renderer(MyRenderer.java)、画正方形的类Rectangle.java、以及一个做shader操作的类ShaderUtil.java

MainActivity.java:

package com.cxy.picturetransfer; 
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle; 
public class MainActivity extends Activity { 
GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
     glSurfaceView = new GLSurfaceView(this);
     // 设置OpenGLES版本为2.0
    glSurfaceView.setEGLContextClientVersion(2);
       // 设置渲染器 渲染模式
       MyRenderer mRenderer = new MyRenderer(glSurfaceView);
       glSurfaceView.setRenderer(mRenderer);
       glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);      
       setContentView(glSurfaceView);
   } 
}

myRender.java

package com.cxy.picturetransfer;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;

public class MyRenderer implements GLSurfaceView.Renderer {
    private Rectangle rect;

    //构找函数
    public MyRenderer(GLSurfaceView glView){
        Log.v("构造函数先执行\n", "");
        //创建三角形对象
        rect = new Rectangle(glView);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {//设置屏幕背景色
        GLES20.glClearColor(0, 0, 0, 1.0f);
        //打开深度检测
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);}

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置视窗大小及位置
        GLES20.glViewport(0, 0, width, height);
        //计算GLSurfaceView的宽高比
        float ratio = (float)width/height;
        /*
         * 产生透视矩阵
         * 参数介绍 : 
         * ① 4 * 4 投影矩阵
         * ② 投影矩阵的起始位置
         * 后面的四个参数分别是 左 右 下 上 的距离
         * 最后两个参数是 近视点 和 远视点 距离
         */
        Matrix.frustumM(Rectangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
        /*
         * 设置摄像机参数矩阵
         * 参数介绍 : 
         * 前两个参数是摄像机参数矩阵 和 矩阵数组的起始位置
         * 后面三个一组是三个空间坐标 先后依次是 摄像机的位置  看的方向 摄像机上方朝向
         */
        Matrix.setLookAtM(Rectangle.mVMatrix, 0, 0f,0f,3f,0f,0f,0f,0f,1.0f,0.0f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清除深度缓冲与颜色缓冲
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        //绘制三角形
        rect.drawSelf();
    }

    //定义一个工具方法,将float[]buffer数据转换为OpenGL ES所需要的FloatBuffer
    public FloatBuffer floatBufferUtil(float[] arr){
        FloatBuffer mbuffer;
        //初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        //数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());

        mbuffer = qbb.asFloatBuffer();
        mbuffer.put(arr);
        mbuffer.position(0);

        return mbuffer;
    }
    //定义一个工具方法,将int[]buffer数据转换为OpenGL ES所需要的IntBuffer
    public IntBuffer intBufferUtil(int[] arr){
        IntBuffer mbuffer;
        //初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        //数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());

        mbuffer = qbb.asIntBuffer();
        mbuffer.put(arr);
        mbuffer.position(0);
        return mbuffer;
    }
}

Rectangle.java

package com.cxy.picturetransfer;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class Rectangle {



    /* 绘制正方形的data */
    float[] rectData = new float[] { 
            0.4f, 0.4f, 0.0f, // 右上顶点
            0.4f, -0.4f, 0.0f, // 右下顶点
            -0.4f, 0.4f, 0.0f, // 左上顶点
            -0.4f, -0.4f, 0.0f // 左下顶点
    };

    FloatBuffer rectDataBuffer;

    public static float[] mProjMatrix = new float[16];    //4 * 4 投影矩阵
    public static float[] mVMatrix = new float[16];        //摄影机位置朝向参数矩阵
    public static float[] mMVPMatrix;                    //最后起作用的总变换矩阵

    int mProgram;                                        //自定义渲染管线着色程序id
    /*
     * 下面的三个变量是顶点着色器中定义的三个变量
     * 其中的总变换矩阵属性 是 一致变量
     * 顶点位置 和 颜色属性 是 属性变量
     */
    int muMVPMatrixHandle;                                //总变换矩阵的引用
    int maPositionHandle;                                //顶点位置属性引用
    int maColorHandle;                                    //顶点颜色属性引用

    String mVertexShader;                                //顶点着色器脚本代码
    String mFragmentShader;                                //片元着色器脚本代码

    //顶点着色器脚本代码
    String mVertexSource = new String(
            "uniform mat4 uMVPMatrix;\n"+ //总变换矩阵
            "attribute vec3 aPosition; \n"+ //顶点位置
            "attribute vec4 aColor;\n"+   //顶点颜色
            "varying  vec4 vColor;\n"+  //用于传递给片元着色器的变量

            "void main()\n"+     
            "{\n"+                                   
               "gl_Position = uMVPMatrix * vec4(aPosition,1);\n"+ //根据总变换矩阵计算此次绘制此顶点位置
               "vColor = aColor;\n"+//将接收的颜色传递给片元着色器 
            "}\n"      
    );   
    //片元着色器脚本代码
    String mFragmentSource = new String(
            "precision mediump float;\n"+
            "varying  vec4 vColor;\n"+ //接收从顶点着色器过来的参数

            "void main()\n"+                         
            "{\n"+                       
               "gl_FragColor = vColor;\n"+//给此片元颜色值
            "}\n"
            );                               

    /*
     * 这个变换矩阵 在设置变换 , 位移 , 旋转的时候 将参数设置到这个矩阵中去
     */
    static float[] mMMatrix = new float[16];            //具体物体的3D变换矩阵, 包括旋转, 平移, 缩放

    int vCount = 0;                                            //顶点数量
    float xAngle = 0;                                        //绕x轴旋转角度

    /**
     * 构造方法
     * 
     * @param mv
     *            GLSurfaceView子类对象, 显示3D画面的载体
     */
    public Rectangle(GLSurfaceView mv) {
        initVertexData();
        initShader(mv);
    }

    public void initVertexData() {
        //设置定点数为3
        vCount = 4; 
        rectDataBuffer = floatBufferUtil(rectData);
    }

    /**
     * 初始化着色器
     * 
     * 流程 : ① 从资源中获取顶点 和 片元着色器脚本 ② 根据获取的顶点 片元着色器脚本创建着色程序 ③ 从着色程序中获取顶点位置引用 ,
     * 顶点颜色引用, 总变换矩阵引用
     * 
     * @param mv
     *            MyTDView对象, 是GLSurfaceView对象
     */
    public void initShader(GLSurfaceView mv) {
        /*
         * mVertextShader是顶点着色器脚本代码
         * 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中
         * 传入的两个参数是 脚本名称 和 应用的资源
         * 应用资源Resources就是res目录下的那写文件
         */
        mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
        mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());

        /*
         * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错
         */
        /*mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);*/
        mProgram = ShaderUtil.createProgram(mVertexSource, mFragmentSource);

        /*
         * 从着色程序中获取 属性变量 顶点坐标(颜色)数据的引用
         * 其中的"aPosition"是顶点着色器中的顶点位置信息
         * 其中的"aColor"是顶点着色器的颜色信息
         */
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "rectData");
        maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");

        /*
         * 从着色程序中获取一致变量  总变换矩阵
         * uMVPMatrix 是顶点着色器中定义的一致变量
         */
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

    }

    /**
     * 绘制三角形方法
     * 
     * 绘制流程 : ① 指定着色程序 ② 设置变换矩阵 ③ 将顶点位置 颜色 数据传进渲染管线 ④ 启动顶点位置 颜色 数据 ⑤ 执行绘制
     */
    public void drawSelf() {
        //根据着色程序id 指定要使用的着色器
        GLES20.glUseProgram(mProgram);
        /*
         * 设置旋转变化矩阵 
         * 参数介绍 : ① 3D变换矩阵 ② 矩阵数组的起始索引 ③旋转的角度 ④⑤⑥
         */
        Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
        /*
         * 设置沿z轴正方向位移
         * 参数介绍 : ① 变换矩阵 ② 矩阵索引开始位置 ③④⑤设置位移方向z轴
         */
        Matrix.translateM(mMMatrix, 0, 0, 0, 1);
        /*
         * 设置绕x轴旋转
         * 参数介绍 : ① 变换矩阵 ② 索引开始位置 ③ 旋转角度 ④⑤⑥ 设置绕哪个轴旋转
         */
        Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);
        /*
         * 应用投影和视口变换
         */
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Rectangle.getFianlMatrix(mMMatrix), 0);
        /*
         * 将顶点位置数据传送进渲染管线, 为画笔指定定点的位置数据
         */

        GLES20.glVertexAttribPointer(
                maPositionHandle, 
                3, 
                GLES20.GL_FLOAT, 
                false, 
                3 * 4, 
                rectDataBuffer
        );
        /*
         * 将顶点颜色数据传送进渲染管线, 为画笔指定定点的颜色数据

        GLES20.glVertexAttribPointer(
                maColorHandle, 
                4, 
                GLES20.GL_FLOAT, 
                false, 
                4 * 4, 
                mColorBuffer
        );*/
        //启用顶点位置数据
        GLES20.glEnableVertexAttribArray(maPositionHandle);
        //启用顶点颜色数据
        GLES20.glEnableVertexAttribArray(maColorHandle);
        //执行绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vCount);
    }

    /**
     * 计算最终投影的矩阵
     * 
     * @param spec
     * @return
     */
    public static float[] getFianlMatrix(float[] spec) {
        mMVPMatrix = new float[16];
        /*
         * 计算矩阵变换投影
         * 
         * 参数介绍 : ① 总变换矩阵 ② 总变换矩阵起始索引 ③ 摄像机位置朝向矩阵 ④ 摄像机朝向矩阵起始索引 ⑤ 投影变换矩阵 ⑥
         * 投影变换矩阵起始索引
         */
        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }

    // 定义一个工具方法,将float[]buffer数据转换为OpenGL ES所需要的FloatBuffer
    public FloatBuffer floatBufferUtil(float[] arr) {
        FloatBuffer mbuffer;
        // 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
        ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
        // 数组排列用nativeOrder
        qbb.order(ByteOrder.nativeOrder());

        mbuffer = qbb.asFloatBuffer();
        mbuffer.put(arr);
        mbuffer.position(0);

        return mbuffer;
    }

}

ShaderUtil.java

package com.cxy.picturetransfer;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;

/*
 * 这个工具类用来加载定点着色器与片元着色器
 */
public class ShaderUtil {

    /**
     * 加载着色器方法
     * 
     * 流程 : 
     * 
     * ① 创建着色器
     * ② 加载着色器脚本
     * ③ 编译着色器
     * ④ 获取着色器编译结果
     * 
     * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
     * @param source 着色脚本字符串
     * @return 返回的是着色器的引用, 返回值可以代表加载的着色器
     */
    public static int loadShader(int shaderType , String source){
        //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
        int shader = GLES20.glCreateShader(shaderType);
        if(shader != 0){
            //2.如果着色器创建成功, 为创建的着色器加载脚本代码
            GLES20.glShaderSource(shader, source);
            //3.编译已经加载脚本代码的着色器
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            //4.获取着色器的编译情况, 如果结果为0, 说明编译失败
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if(compiled[0] == 0){
                 Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
                 Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
                 //编译失败的话, 删除着色器, 并显示log
                 GLES20.glDeleteShader(shader);
                 shader = 0;
            }
        }
        else{
            Log.e("ES20_ERROR", "Could Create shader " + shaderType + ":"+
                    "Error:"+ shader);
        }
        return shader;
    }

    /**
     * 检查每一步的操作是否正确
     * 
     * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
     * 
     * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器, 
     *      使glAttachShader()方法, 那么这个参数就是"glAttachShader"
     */
    public static void checkGLError(String op){
        int error;
        //错误代码不为0, 就打印错误日志, 并抛出异常
        while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){
             Log.e("ES20_ERROR", op + ": glError " + error);
             throw new RuntimeException(op + ": glError " + error);
        }
    }

    /**
     * 创建着色程序
     * 
     * ① 加载顶点着色器
     * ② 加载片元着色器
     * ③ 创建着色程序
     * ④ 向着色程序中加入顶点着色器
     * ⑤ 向着色程序中加入片元着色器
     * ⑥ 链接程序
     * ⑦ 获取链接程序结果
     * 
     * @param vertexSource      定点着色器脚本字符串
     * @param fragmentSource    片元着色器脚本字符串
     * @return
     */
    public static int createProgram(String vertexSource , String fragmentSource){
        //1. 加载顶点着色器, 返回0说明加载失败
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if(vertexShader == 0)
        {
            Log.e("ES20_ERROR", "加载顶点着色器失败");           
            return 0;
        }
        //2. 加载片元着色器, 返回0说明加载失败
        int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if(fragShader == 0)
        {
            Log.e("ES20_ERROR", "加载片元着色器失败");
            return 0;
        }
        //3. 创建着色程序, 返回0说明创建失败
        int program = GLES20.glCreateProgram();
        if(program != 0){
            //4. 向着色程序中加入顶点着色器
            GLES20.glAttachShader(program, vertexShader);
            //检查glAttachShader操作有没有失败
            checkGLError("glAttachShader");
            //5. 向着色程序中加入片元着色器
            GLES20.glAttachShader(program, fragShader);
            //检查glAttachShader操作有没有失败
            checkGLError("glAttachShader");

            //6. 链接程序
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            //获取链接程序结果
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if(linkStatus[0] != GLES20.GL_TRUE){
                Log.e("ES20.ERROR", "链接程序失败 : ");
                Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));
                //如果链接程序失败删除程序
                GLES20.glDeleteProgram(program);
                program = 0;
            }           
        }
        else{
            Log.e("ES20_ERROR", "glCreateProgram Failed: "+program);
        }

        return program;
    }

    /**
     * 从assets中加载着色脚本
     * 
     * ① 打开assets目录中的文件输入流
     * ② 创建带缓冲区的输出流
     * ③ 逐个字节读取文件数据, 放入缓冲区
     * ④ 将缓冲区中的数据转为字符串
     * 
     * @param fileName assets目录中的着色脚本文件名
     * @param resources 应用的资源
     * @return
     */
    public static String loadFromAssetsFile(String fileName, Resources resources){
        String result = null;
        try {
            //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流
            InputStream is = resources.getAssets().open(fileName);
            int ch = 0;
            //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中
            while((ch = is.read()) != -1){
                baos.write(ch);
            }
            //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
            byte[] buffer = baos.toByteArray();
            baos.close();
            is.close();
            result = new String(buffer, "UTF-8");
            result = result.replaceAll("\\r\\n", "\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

错误说明

call to opengl api with no current context.

解决方法
我把 MyRenderer.java中的 构造函数中的 rect = new Rectangle(glView);这句代码,放到了OnSurfaceCreate()函数里就没报这个错误了

但是有一点我不理解,就是难道是OnSurfaceCreate()函数自己起了一个线程吗? ????
构造函数所在的线程,属于 UI线程????

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenGL ES 3.0 英文版 第1章——OpenGL ES 3.0简介   第1章简单介绍OpenGL ES,概述了OpenGL ES 3.0图形管线,讨论了OpenGL ES 3.0的设计理念和限制,最后介绍了OpenGL ES 3.0中使用的一些约定和类型。   第2章——你好,三角形:一个OpenGL ES 3.0示例   第2章介绍绘制三角形的一个简单OpenGL ES 3.0示例。我们的目的是说明OpenGL ES 3.0程序的样子,向读者介绍一些API概念,并说明如何构建和运行OpenGL ES 3.0示例程序。   第3章——EGL简介   第3章介绍EGL——为OpenGL ES 3.0创建表面和渲染上下文的API。我们说明与原生窗口系统通信、选择配置和创建EGL渲染上下文及表面的方法,传授足够多的EGL知识,你可以了解到启动OpenGL ES 3.0进行渲染所需的所有知识。   第4章——着色器和程序   着色器对象和程序对象是OpenGL ES 3.0中最基本的对象。第4章介绍创建着色器对象、编译着色器和检查编译错误的方法。这一章还说明如何创建程序对象、将着色器对象连接到程序对象以及链接最终程序对象的方法。我们讨论如何查询程序对象的信息以及加载统一变量(uniform)的方法。此外,你将学习有关源着色器和程序二进制代码之间的差别以及它们的使用方法。   第5章——OpenGL ES着色语言   第5章介绍编写着色器所需的着色语言的基础知识。这些着色语言基础知识包括变量和类型、构造器、结构、数组、统一变量、统一变量块(uniform block)和输入/输出变量。该章还描述着色语言的某些更细微的部分,例如精度限定符和不变性。   第6章——顶点属性、顶点数组和缓冲区对象   从第6章开始(到第11章为止),我们将详细介绍管线,教授设置和编程图形管线各个部分的方法。这一旅程从介绍几何形状输入图形管线的方法开始,包含了对顶点属性、顶点数组和缓冲区对象的讨论。   第7章——图元装配和光栅化   在前一章讨论几何形状输入图形管线的方法之后,第7章将讨论几何形状如何装配成图元,介绍OpenGL ES 3.0中所有可用的图元类型,包括点精灵、直线、三角形、三角形条带和三角扇形。此外,我们还说明了在顶点上进行坐标变换的方法,并简单介绍了OpenGL ES 3.0管线的光栅化阶段。   第8章——顶点着色器   我们所介绍的管线的下一部分是顶点着色器。第8章概述了顶点着色器如何融入管线以及OpenGL ES 着色语言中可用于顶点着色器的特殊变量,介绍了多个顶点着色器的示例,包括逐像素照明和蒙皮(skinning)。我们还给出了用顶点着色器实现OpenGL ES 1.0(和1.1)固定功能管线的示例。   第9章——纹理   第9章开始介绍片段着色器,描述OpenGL ES 3.0中所有可用的纹理功能。该章提供了创建纹理、加载纹理数据以及纹理渲染的细节,描述了纹理包装模式、纹理过滤、纹理格式、压缩纹理、采样器对象、不可变纹理、像素解包缓冲区对象和Mip贴图。该章介绍了OpenGL ES 3.0支持的所有纹理类型:2D纹理、立方图、2D纹理数组和3D纹理。   第10章——片段着色器   第9章的重点是如何在片段着色器中使用纹理,第10章介绍编写片段着色器所需知道的其他知识。该章概述了片段着色器和所有可用的特殊内建变量,还演示了用片段着色器实现OpenGL ES 1.1中所有固定功能技术的方法。多重纹理、雾化、Alpha测试和用户裁剪平面的例子都使用片段着色器实现。   第11章——片段操作   第11章讨论可以适用于整个帧缓冲区或者在OpenGL ES 3.0片段管线中执行片段着色器后适用于单个片段的操作。这些操作包括剪裁测试、模板测试、深度测试、多重采样、混合和抖动。本章介绍OpenGL ES 3.0图形管线的最后阶段。   第12章——帧缓冲区对象   第12章讨论使用帧缓冲区对象渲染屏幕外表面。帧缓冲区对象有多种用法,最常见的是渲染到一个纹理。本章提供API帧缓冲区对象部分的完整概述。理解帧缓冲区对象对于实现许多高级特效(如反射、阴影贴图和后处理)至关重要。   第13章——同步对象和栅栏   第13章概述同步对象和栅栏,它们是在OpenGL ES 3.0主机应用和GPU执行中同步的有效图元。我们讨论同步对象和栅栏的使用方法,并以一个示例作为结束。   第14章——OpenGL ES 3.0高级编程   第14章是核心章节,将本书介绍的许多主题串联在一起。我们已经选择了高级渲染技术的一个样本,并展示了实现这些功能的示例。该章包含使用法线贴图的逐像素照明、环境贴图、粒子系统、图像后处理、程序纹理、阴影贴图、地形渲染

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值